use std::collections::BTreeMap;

use crate::QueryValue;
use crate::UrlEncoding;

#[derive(Debug)]
pub struct QueryParam {
    name: String,
    val: QueryValue,
}

impl QueryParam {
    pub fn new(name: String, val: QueryValue) -> Self {
        Self { name, val}
    }
}

impl QueryString for QueryParam {
    fn qs(&self) -> String {
        let Self {name, val} = self;
        match &val {
            QueryValue::Integer(v) => format!("{}={}", name, v),
            QueryValue::Float(v) => format!("{}={}", name, v),
            QueryValue::String(v) => format!("{}={}", name, v.url_encode()),
            QueryValue::Array(v) => v.into_iter().map(|it| format!("{}[]={}", name, it.url_encode())).collect::<Vec<String>>().join("&"),
            QueryValue::Null => format!("{}=", name),
        }
    }
}


pub struct QueryParams {
    params: BTreeMap<String, QueryParam>
}

impl QueryParams {
    pub fn new() -> Self {
        Self {
            params: BTreeMap::new(),
        }
    }

    pub fn get(&self, name: &String) -> Option<&QueryParam> {
        self.params.get(name)
    }

    pub fn get_mut(&mut self, name: &String) -> Option<&mut QueryParam> {
        self.params.get_mut(name)
    }

    pub fn insert(&mut self, param: QueryParam) {
        let n = param.name.clone();
        self.params.insert(n, param);
    }

    pub fn remove(&mut self, name: &String) -> bool {
        !matches!(self.params.remove(name), None)
    }
}

impl QueryString for QueryParams {
    fn qs(&self) -> String {
        self.params.values().into_iter().map(|v| v.qs()).collect::<Vec<String>>().join("&")
    }
}

pub trait QueryString {
    fn qs(&self) -> String;
}


impl QueryString for i8 {
    fn qs(&self) -> String {
        self.to_string()
    }
}

impl QueryString for i16 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}

impl QueryString for i32 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for i64 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for u8 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for u16 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}

impl QueryString for u32 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for u64 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for u128 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for f32 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for f64 {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for isize {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for bool {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for usize {
    fn qs(&self) -> String {
        self.url_encode()
    }
}
impl QueryString for &str {
    fn qs(&self) -> String {
        self.url_encode()
    }
}

impl QueryString for String {
    fn qs(&self) -> String {
        self.url_encode()
    }
}

impl<T: QueryString> QueryString for Option<T> {
    fn qs(&self) -> String {
        match self {
            Some(ref v) => v.qs(),
            None => "".to_string(),
        }
    }
}

impl<T: QueryString> QueryString for BTreeMap<String, T> {
    fn qs(&self) -> String {
        self.into_iter()
            .map(|(k, v)| format!("{}={}", k.qs(), v.qs()))
            .collect::<Vec<String>>()
            .as_slice()
            .join("&")
    }
}


impl<T: QueryString> QueryString for BTreeMap<&str, T> {
    fn qs(&self) -> String {
        self.into_iter()
            .map(|(k, v)| format!("{}={}", k.qs(), v.qs()))
            .collect::<Vec<String>>()
            .as_slice()
            .join("&")
    }
}